package org.xx.armory.web.dispatchers;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.web.MIMETypes;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Objects;

public class CoreDispatcher
        implements Filter {
    public static final String ORIGINAL_PATH_KEY = "__original_path__";
    public static final String WEB_CONTENT_PREFIX = "/web";
    private static final String DEFAULT_RESOURCE_NAME = "index.html";
    private final Logger logger = LoggerFactory.getLogger(CoreDispatcher.class);

    public CoreDispatcher() {
    }

    @Override
    public void init(
            FilterConfig config
    )
            throws ServletException {
        final Enumeration<?> parameterNameEnum = config.getInitParameterNames();

        while (parameterNameEnum.hasMoreElements()) {
            final String parameterName = (String) parameterNameEnum.nextElement();
            config.getServletContext().setAttribute("__" + parameterName + "__", config.getInitParameter(parameterName));
        }

    }

    @Override
    public void doFilter(
            ServletRequest req,
            ServletResponse rsp,
            FilterChain chain
    )
            throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) rsp;
        String path = this.getPathInfo(request);
        this.logger.debug("url={}, host={}", path, request.getRemoteAddr());
        if (path.endsWith("/")) {
            this.writeRedirect(response, path + DEFAULT_RESOURCE_NAME);
        } else {
            final ServletContext ctx = request.getServletContext();
            final String originalPath = path;
            path = "/web" + path;
            String ext = "." + FilenameUtils.getExtension(path).toLowerCase();
            if (".".equals(ext)) {
                ext = ".html";
            }
            if (!".htm".equals(ext) && !".html".equals(ext)) {
                URL url = ctx.getResource(path);
                if (url != null) {
                    long lastModified = url.openConnection().getLastModified();
                    if (lastModified > request.getDateHeader("if-modified-since")) {
                        this.writeStatic(response, url, ext);
                    } else {
                        this.writeNotModified(response);
                    }
                } else {
                    this.logger.warn("cannot find resource: {}", path);
                    this.writeNotFound(response);
                }
            } else {
                request.setAttribute("__original_path__", originalPath);
                this.writeHtml(request, response, path);
            }
        }

    }

    @Override
    public void destroy() {
    }

    private String getPathInfo(
            HttpServletRequest request
    ) {
        Objects.requireNonNull(request, "request cannot be null");
        String ret = request.getPathInfo();
        if (ret == null) {
            ret = request.getServletPath();
        } else {
            ret = request.getServletPath() + ret;
        }

        return StringUtils.isBlank(ret) ? "/" : ret.trim();
    }

    private void writeStatic(
            HttpServletResponse response,
            URL url,
            String ext
    )
            throws IOException {
        Objects.requireNonNull(response, "response cannot be null");
        Objects.requireNonNull(url, "url cannot be null");
        response.setStatus(200);
        response.addDateHeader("date", (new Date()).getTime());
        response.addHeader("cache-control", "public,max-age=3600");
        URLConnection urlConnection = url.openConnection();
        if (urlConnection.getLastModified() > 0L) {
            response.addDateHeader("last-modified", urlConnection.getLastModified());
        }

        response.setContentType(MIMETypes.getContentType(ext));
        response.setContentLength(urlConnection.getContentLength());
        InputStream urlStream = url.openStream();
        Throwable var6 = null;

        try {
            IOUtils.copy(urlStream, response.getOutputStream());
        } catch (Throwable var15) {
            var6 = var15;
            throw var15;
        } finally {
            if (urlStream != null) {
                if (var6 != null) {
                    try {
                        urlStream.close();
                    } catch (Throwable var14) {
                        var6.addSuppressed(var14);
                    }
                } else {
                    urlStream.close();
                }
            }

        }

    }

    private void writeHtml(
            HttpServletRequest request,
            HttpServletResponse response,
            String path
    )
            throws IOException, ServletException {
        Objects.requireNonNull(request, "request cannot be null");
        Objects.requireNonNull(response, "response cannot be null");
        response.setStatus(200);
        response.addHeader("cache-control", "public,max-age=3600");
        response.addDateHeader("last-modified", (new Date()).getTime());
        String jsp = FilenameUtils.removeExtension(path) + ".jsp";
        this.logger.debug("[{}] -> {}", path, jsp);
        request.getRequestDispatcher(jsp).forward(request, response);
    }

    private void writeRedirect(
            HttpServletResponse response,
            String path
    )
            throws IOException {
        Objects.requireNonNull(response, "response cannot be null");
        Objects.requireNonNull(path, "path cannot be null");
        response.sendRedirect(path);
    }

    private void writeNotModified(HttpServletResponse response)
            throws IOException {
        Objects.requireNonNull(response, "response cannot be null");
        response.sendError(304);
    }

    private void writeNotFound(HttpServletResponse response)
            throws IOException {
        Objects.requireNonNull(response, "response cannot be null");
        response.sendError(404);
    }
}
